<html>
 <head>
  <meta charset="UTF-8">
 </head>
 <body>
  <h1 data-lake-id="o4SWJ" id="o4SWJ"><span data-lake-id="u20ba2bc0" id="u20ba2bc0">典型回答</span></h1>
  <p data-lake-id="ue2627f35" id="ue2627f35"><br></p>
  <p data-lake-id="u4345e4e6" id="u4345e4e6"><span data-lake-id="u1982dda0" id="u1982dda0">所谓的MySQL事务的2阶段提交，其实是在更新过程中，保证binlog和redolog一致性的一种手段。</span></p>
  <p data-lake-id="u4972adc6" id="u4972adc6"><br></p>
  <p data-lake-id="u467e357f" id="u467e357f"><img src="https://cdn.nlark.com/yuque/0/2023/png/5378072/1692967297757-6f21cd6f-ca3e-4bf9-b890-cb2e47ef9ec9.png?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_18%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u1e68f6b8" id="u1e68f6b8"><br></p>
  <p data-lake-id="u22beee54" id="u22beee54"><span data-lake-id="u65775608" id="u65775608">上图中右侧部分即为2阶段提交。他的过程是：</span></p>
  <p data-lake-id="u79fc653b" id="u79fc653b"><span data-lake-id="u73fe65ec" id="u73fe65ec">​</span><br></p>
  <ul list="u66842382">
   <li fid="ud2d91106" data-lake-id="ub6acc390" id="ub6acc390"><span data-lake-id="u2aaaa8c2" id="u2aaaa8c2">Prepare 阶段</span></li>
  </ul>
  <ul list="u66842382" data-lake-indent="1">
   <li fid="ud2d91106" data-lake-id="u66a24e86" id="u66a24e86"><span data-lake-id="ueb3fe3af" id="ueb3fe3af">这个阶段 SQL 已经成功执行并生成 redolog，处于prepare阶段</span></li>
  </ul>
  <ul list="u66842382" start="2">
   <li fid="ud2d91106" data-lake-id="ud83e062e" id="ud83e062e"><span data-lake-id="ua4fe97fb" id="ua4fe97fb">BinLog持久化</span></li>
  </ul>
  <ul list="u66842382" data-lake-indent="1">
   <li fid="ud2d91106" data-lake-id="u93319c48" id="u93319c48"><span data-lake-id="u5a2c6c07" id="u5a2c6c07">binlog 提交，通过 </span><code data-lake-id="u11a7e52e" id="u11a7e52e"><span data-lake-id="u025bb91c" id="u025bb91c">write()</span></code><span data-lake-id="u8fe3f094" id="u8fe3f094"> 将 binlog 内存日志数据写入文件缓冲区；</span></li>
   <li fid="ud2d91106" data-lake-id="u5a51c243" id="u5a51c243"><span data-lake-id="u64ea4f31" id="u64ea4f31">通过</span><code data-lake-id="u553c5eed" id="u553c5eed"><span data-lake-id="u05955569" id="u05955569">fsync() </span></code><span data-lake-id="uf6db1b4d" id="uf6db1b4d">将 binlog 从文件缓冲区永久写入磁盘；</span></li>
  </ul>
  <ul list="u66842382" start="3">
   <li fid="ud2d91106" data-lake-id="u03d3203a" id="u03d3203a"><span data-lake-id="u70cc4fa3" id="u70cc4fa3">Commit</span></li>
  </ul>
  <ul list="u66842382" data-lake-indent="1">
   <li fid="ud2d91106" data-lake-id="ub3c6c383" id="ub3c6c383"><span data-lake-id="u7542602d" id="u7542602d">在执行引擎内部执行事务操作，更新redolog，处于Commit阶段。</span></li>
  </ul>
  <p data-lake-id="u407f32fc" id="u407f32fc"><span data-lake-id="u6fc36812" id="u6fc36812">​</span><br></p>
  <p data-lake-id="ud3c1147a" id="ud3c1147a"><span data-lake-id="ue2266a13" id="ue2266a13">​</span><br></p>
  <blockquote data-lake-id="u2bb050a1" id="u2bb050a1">
   <p data-lake-id="u648fa07f" id="u648fa07f"><span data-lake-id="u1c87c2e4" id="u1c87c2e4">write 和 fsync 是与文件系统和磁盘IO相关的两个不同的操作。</span></p>
   <p data-lake-id="u071b3943" id="u071b3943"><span data-lake-id="u049f18d8" id="u049f18d8">write 操作将数据写入文件的缓冲区，这意味着 write 操作完成后，并不一定立即将数据持久化到磁盘上，而是将数据暂时存储在内存中。</span></p>
   <p data-lake-id="ud455cc73" id="ud455cc73"><span data-lake-id="u78ec4cb0" id="u78ec4cb0">fsync 用于强制将文件的修改持久化到磁盘上。它通常与 write 配合使用，以确保文件的修改在 fsync 操作完成后被写入磁盘。</span></p>
  </blockquote>
  <p data-lake-id="u5ad5b17e" id="u5ad5b17e"><br></p>
  <p data-lake-id="ucc97e886" id="ucc97e886"><span data-lake-id="u1e4879d7" id="u1e4879d7">那么，为什么这个过程需要用2阶段提交的方式呢？</span></p>
  <p data-lake-id="u52612236" id="u52612236"><br></p>
  <p data-lake-id="u1b55c1cd" id="u1b55c1cd"><span data-lake-id="ub8baa4a6" id="ub8baa4a6">假设我们执行一条SQL语句，修改他的name为Hollis ：</span><code data-lake-id="u278a7e84" id="u278a7e84"><span data-lake-id="uc12c733a" id="uc12c733a"> update user set name = 'hollis' where id = 10</span></code><span data-lake-id="ucd22b358" id="ucd22b358"> 。</span></p>
  <p data-lake-id="u39c74599" id="u39c74599"><span data-lake-id="u6d463b0a" id="u6d463b0a">​</span><br></p>
  <p data-lake-id="u7631cade" id="u7631cade"><span data-lake-id="ua14531c7" id="ua14531c7">假设先写入redo log 成功，但是没来得及写入bin log ，系统崩了。在MySQL重启后，可以根据redolog把记录更新成'hollis'，但是，binlog由于没写成功，所以他是没有记录下来这次变更的，那么也就意味着，主备同步的时候，是缺了一条SQL的，导致主备库之间数据不一致。</span></p>
  <p data-lake-id="u33f6757e" id="u33f6757e"><span data-lake-id="u793f7113" id="u793f7113">​</span><br></p>
  <p data-lake-id="ud7ca14af" id="ud7ca14af"><span data-lake-id="udf4681e1" id="udf4681e1">那么，如果换个顺序，先写入binlog成功，但是没来及的写入redolog，系统崩了。在MySQL重启之后，崩溃恢复的时候由于redo log还没写，所以什么都不用做，数据库记录还是旧值。但是因为binlog已经写入成功了，所以在做主备同步的时候，就会把新值同步到备库，就导致了主备库之间数据不一致。</span></p>
  <p data-lake-id="u3a0ec583" id="u3a0ec583"><br></p>
  <p data-lake-id="uf148af22" id="uf148af22"><span data-lake-id="u1e294aac" id="u1e294aac">如上面的例子，</span><strong><span data-lake-id="u01b05e58" id="u01b05e58">如果不引入二阶段提交的话，在bin log 和redo log没办法保证一致性的情况下，就会导致主备库之间的数据不一致。</span></strong></p>
  <p data-lake-id="u0e8650c3" id="u0e8650c3"><span data-lake-id="ued7c4dd8" id="ued7c4dd8">​</span><br></p>
  <p data-lake-id="u41f8d217" id="u41f8d217"><span data-lake-id="ub470e105" id="ub470e105">而为了解决这个问题，那就引入了2阶段提交，来整体的控制redo log 和 bin log的一致性写入。</span></p>
  <p data-lake-id="u71331aa8" id="u71331aa8"><br></p>
  <h1 data-lake-id="bDyeY" id="bDyeY"><span data-lake-id="u26ef7745" id="u26ef7745">扩展知识</span></h1>
  <p data-lake-id="ud34f72ec" id="ud34f72ec"><br></p>
  <h2 data-lake-id="Qolpd" id="Qolpd"><span data-lake-id="u7b91baa8" id="u7b91baa8">2阶段如何保证一致性的？</span></h2>
  <p data-lake-id="u17350f9c" id="u17350f9c"><br></p>
  <p data-lake-id="ube64e333" id="ube64e333"><span data-lake-id="u626d0dd3" id="u626d0dd3">引入2阶段提交之后，事务的提交过程就可能有以下三种情况：</span></p>
  <p data-lake-id="u4be1f22a" id="u4be1f22a"><br></p>
  <p data-lake-id="uf6140d81" id="uf6140d81"><span data-lake-id="u072ffd39" id="u072ffd39">情况一：一阶段提交之后崩溃了，即写入 redo log，处于 prepare 状态 的时候崩溃了</span></p>
  <p data-lake-id="u0ff6cd09" id="u0ff6cd09"><span data-lake-id="ufc1e0aa1" id="ufc1e0aa1">​</span><br></p>
  <p data-lake-id="ud8de9a60" id="ud8de9a60"><span data-lake-id="u2b7c3402" id="u2b7c3402">此时已经写了redolog，处于prepare状态，binlog还没写。这时候如果崩溃恢复，直接回滚事务即可，这样主备是一致的，就都没有执行这个事务。</span></p>
  <p data-lake-id="u75d2ef47" id="u75d2ef47"><span data-lake-id="uee5b2b7e" id="uee5b2b7e">​</span><br></p>
  <p data-lake-id="u67d0fba7" id="u67d0fba7"><span data-lake-id="u83f0acb0" id="u83f0acb0">情况二：一阶段提交成功，写完 binlog 之后崩溃了</span></p>
  <p data-lake-id="uf0ada892" id="uf0ada892"><span data-lake-id="ua0e40e0a" id="ua0e40e0a">​</span><br></p>
  <p data-lake-id="u1b6d37c5" id="u1b6d37c5"><span data-lake-id="ua81be508" id="ua81be508">此时，redolog处于prepare状态，binlog已写入，这时候检查 binlog 中的事务是否存在并且完整，如果存在且完整，则直接提交事务，如果不存在或者不完整，则回滚事务。</span></p>
  <p data-lake-id="u9c3d9268" id="u9c3d9268"><span data-lake-id="u95995c4f" id="u95995c4f">​</span><br></p>
  <p data-lake-id="uef519edb" id="uef519edb"><span data-lake-id="u8d130958" id="u8d130958">情况三：</span><span data-lake-id="ue843797a" id="ue843797a">假设 redolog 处于 commit 状态的时候崩溃了，那么重启后的处理方案同情况二。</span></p>
  <p data-lake-id="uc1a69b9e" id="uc1a69b9e"><span data-lake-id="u25dd1f5c" id="u25dd1f5c">​</span><br></p>
  <p data-lake-id="u6785be8e" id="u6785be8e"><span data-lake-id="u9b5f1f0c" id="u9b5f1f0c">由此可见，两阶段提交能够确保数据的一致性。</span></p>
  <h2 data-lake-id="F4Csh" id="F4Csh"><span data-lake-id="u3675a780" id="u3675a780" style="color: rgb(18, 18, 18)">如何判断binlog和redolog达成一致了？</span></h2>
  <p data-lake-id="u6182a264" id="u6182a264"><br></p>
  <p data-lake-id="ubcff9232" id="ubcff9232"><span data-lake-id="u31fc268e" id="u31fc268e" style="color: rgb(18, 18, 18)">当MySQL写完redolog并将它标记为prepare状态时，并且会在redolog中记录一个XID，它全局唯一的标识着这个事务。而当你设置</span><code data-lake-id="ubae60cd1" id="ubae60cd1"><span data-lake-id="u40bf9d52" id="u40bf9d52" style="color: rgb(18, 18, 18)">sync_binlog=1</span></code><span data-lake-id="ucc48ffee" id="ucc48ffee" style="color: rgb(18, 18, 18)">时，做完了上面第一阶段写redolog后，mysql就会对应binlog并且会直接将其刷新到磁盘中。</span></p>
  <p data-lake-id="u9fc68857" id="u9fc68857"><span data-lake-id="ub9e87d3c" id="ub9e87d3c" style="color: rgb(18, 18, 18)">​</span><br></p>
  <p data-lake-id="uac8afcb3" id="uac8afcb3"><span data-lake-id="uf56473da" id="uf56473da" style="color: rgb(18, 18, 18)">下图就是磁盘上的row格式的binlog记录。binlog结束的位置上也有一个XID。</span></p>
  <p data-lake-id="uc8c501e8" id="uc8c501e8"><span data-lake-id="uc046ba6a" id="uc046ba6a" style="color: rgb(18, 18, 18)">​</span><br></p>
  <p data-lake-id="u4c05cceb" id="u4c05cceb"><img src="https://cdn.nlark.com/yuque/0/2023/webp/5378072/1692963784172-6c7f5bca-2bcc-416b-a08e-c215df58bd51.webp?x-oss-process=image%2Fwatermark%2Ctype_d3F5LW1pY3JvaGVp%2Csize_21%2Ctext_SmF2YSA4IEd1IFA%3D%2Ccolor_FFFFFF%2Cshadow_50%2Ct_80%2Cg_se%2Cx_10%2Cy_10"></p>
  <p data-lake-id="u5841c34b" id="u5841c34b"><span data-lake-id="uae6e1919" id="uae6e1919">图片参考：知乎@授人以渔 </span></p>
  <p data-lake-id="u148f4dc0" id="u148f4dc0"><span data-lake-id="ua57ef80c" id="ua57ef80c" style="color: rgb(18, 18, 18)">​</span><br></p>
  <p data-lake-id="u94c8be31" id="u94c8be31"><span data-lake-id="u1664aa57" id="u1664aa57" style="color: rgb(18, 18, 18)">只要这个XID和redolog中记录的XID是一致的，MySQL就会认为binlog和redolog逻辑上是一致的。</span></p>
  <p data-lake-id="uc8e57fb2" id="uc8e57fb2"><span data-lake-id="u361a8ba3" id="u361a8ba3" style="color: rgb(18, 18, 18)">​</span><br></p>
  <p data-lake-id="u3a1d38de" id="u3a1d38de"><br></p>
 </body>
</html>